package org.jradweb.core.web;

import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.hibernate.validator.InvalidValue;
import org.jradweb.core.beans.BeansException;
import org.jradweb.core.beans.JBeanWrapperImpl;
import org.jradweb.core.orm.JModel;
import org.jradweb.core.utils.StringUtils;
import org.springframework.beans.TypeMismatchException;

/**
 * 
 * 
 * Created On 2008-7-9
 * 
 * @author <a href="mailto:wagzhi@gmail.com">Paul Wang</a>
 * @version $Id: FormBeanImpl.java 149 2008-10-10 03:10:10Z wagzhi $
 */
public class FormBeanImpl<T> extends JBeanWrapperImpl implements FormBean<T> {

	private static final String MESSAGE_KEY_SPLIT_CHAR = ".";
	private static final String MESSAGE_KEY_TYPE_MISMATCH = "typeMismatch";

	private RequestParameterValues requestParameterValues = new RequestParameterValues();
	private Locale locale;

	private ResourceBundle resourceBundle;

	public ResourceBundle getResourceBundle() {
		if (this.resourceBundle == null) {
			this.resourceBundle = this.getResourceBundleInternal(locale);
		}
		return resourceBundle;
	}

	public void setResourceBundle(ResourceBundle resourceBundle) {
		this.resourceBundle = resourceBundle;
	}

	// TODO make it configurable
	private static final String VALIDATOR_MESSAGE = "ValidatorMessages";

	/**
	 * save property value from request
	 * 
	 * @return
	 */
	public RequestParameterValues getPropertyValues() {
		return requestParameterValues;
	}

	@Override
	public void setPropertyValue(String propertyName, Object value)
			throws BeansException {
		RequestParameterValue pv = new RequestParameterValue();
		pv.setName(propertyName);
		pv.setValue(value);

		// set to bean
		if (this.isWritableProperty(propertyName)) {
			this.registerEditorByAnnotation(propertyName);			
			
			pv.setBeanProperty(true);
			try {
				super.setPropertyValue(propertyName, value);
			} catch (TypeMismatchException e) {
				ResourceBundle rb = this.getResourceBundle();
				// messasge key like: user.name.typeMismatch
				String propertyValueTypeMismatchKey = StringUtils
						.getLowerCaseFirstClassName(this.getWrappedClass())
						+ MESSAGE_KEY_SPLIT_CHAR
						+ propertyName
						+ MESSAGE_KEY_SPLIT_CHAR
						+ MESSAGE_KEY_TYPE_MISMATCH;
				String message = propertyValueTypeMismatchKey; // if no value
				// found in
				// resource file
				// ,use key.
				try {
					message = rb.getString(propertyValueTypeMismatchKey);
				} catch (MissingResourceException e1) {
					// if value of the propertyName key not found ,use value of
					// key "typeMismatch"
					message = rb.getString(MESSAGE_KEY_TYPE_MISMATCH);
				}
				pv.setErrorMessage(message);
			}

		}

		this.requestParameterValues.add(pv);
	}

	/**
	 * some validator need access database
	 */
	public void validator() {
		// hibernate validator
		// TODO use a invalidatable interface is better than JModel
		if (this.getWrappedInstance() != null
				&& JModel.class.isInstance(this.getWrappedInstance())) {
			JModel model = (JModel) this.getWrappedInstance();
			InvalidValue[] invalidValues = model.getInvalidValues(locale);

			for (String key : this.requestParameterValues.keySet()) {
				for (InvalidValue iv : invalidValues) {
					if (iv.getPropertyName().equals(key)) {
						RequestParameterValue rpv = this.requestParameterValues
								.get(key);

						if (rpv.getErrorMessage() == null) {// maybe alread has
							// type mismatch
							// message, keep it
							rpv.setErrorMessage(iv.getMessage());
						}
					}
				}
			}

			// TODO validate nested jmodel objects
		}
	}

	public <T> FormBeanImpl(T object, Locale locale) {
		super(object);
		this.locale = locale;
	}

	private ResourceBundle getResourceBundleInternal(Locale locale) {
		Locale currentLocale = locale == null ? Locale.getDefault() : locale;

		ResourceBundle rb = null;
		try {
			// use context class loader as a first citizen
			ClassLoader contextClassLoader = Thread.currentThread()
					.getContextClassLoader();
			if (contextClassLoader == null) {
				throw new MissingResourceException("No context classloader",
						null, VALIDATOR_MESSAGE);
			}
			rb = ResourceBundle.getBundle(VALIDATOR_MESSAGE, currentLocale,
					contextClassLoader);
		} catch (MissingResourceException e) {
			throw e;
		}
		return rb;
	}

	public boolean getHasError() {
		for (RequestParameterValue rpv : this.getPropertyValues().values()) {
			if (rpv.getErrorMessage() != null) {
				return true;
			}
		}
		return false;
	}

	public Locale getLocale() {
		return locale;
	}

	public void setLocale(Locale locale) {
		this.locale = locale;
	}

	public T getBean() {
		return (T) this.getWrappedInstance();
	}

	public String getPropertyValueAsText(String propertyName) {
		// requestValue.value maybe String, String[] or , InputStream
		// when requestValue is error ,return value from requestValue map
		RequestParameterValue requestValue = this.requestParameterValues
				.get(propertyName);
		if (requestValue != null && requestValue.getValue() != null
				&& String.class.isInstance(requestValue.getValue())
				&& requestValue.getErrorMessage() != null) {
			return (String) requestValue.getValue();
		}

		// get value from wrapped object
		Object value = this.getPropertyValue(propertyName);
		if (value != null) {
			if(String.class.isInstance(value)){
				return (String)value;
			}
			
			
			PropertyEditorSupport editor = (PropertyEditorSupport) this
					.findCustomEditor(value.getClass(), propertyName);
			if (editor == null) {
				editor = (PropertyEditorSupport) this.getDefaultEditor(value.getClass());
			}
			if(editor==null){
				throw new TypeMismatchException(value,value.getClass());
			}
			editor.setValue(value);
			return editor.getAsText();
		}

		return null;
	}
	
	@Override
	public Object getPropertyValue(String propertyName) throws BeansException {
		this.registerEditorByAnnotation(propertyName);
		return super.getPropertyValue(propertyName);
	}
	
	private Map<String,Class> registerCache=new HashMap<String,Class>();
	protected void registerEditorByAnnotation(String propertyName){
		//read from cache
		Class type=this.getPropertyType(propertyName);
		if(registerCache.keySet().contains(propertyName)){
			return;
		}
		
		//read from annotation
		this.registerCache.put(propertyName, null);
		Class propertyEditor=this.getPropertyEditorByAnnotation(propertyName);
		if(propertyEditor!=null){
			try {
				this.registerCustomEditor(type, propertyName, (PropertyEditor)propertyEditor.newInstance());
			} catch (InstantiationException e) {
				throw new IllegalArgumentException("PropertyEditor class "+propertyEditor.getName()+" must have default construct!",e);
			} catch (IllegalAccessException e) {
				throw new IllegalArgumentException("PropertyEditor class "+propertyEditor.getName()+" must have default construct!",e);
			}
		}
	}
	private static Map<String,Class> propertyEditorCache=new HashMap<String,Class>();
	protected Class getPropertyEditorByAnnotation(String propertyName){		
		//cache, may be some problem when hot deploy
		String pathName=this.getWrappedClass().getName()+"."+propertyName;
//		if(propertyEditorCache.keySet().contains(pathName)){
//			return propertyEditorCache.get(pathName);
//		}
		
		
		PropertyDescriptor pd=this.getPropertyDescriptor(propertyName);
		
		Method readMethod=pd.getReadMethod();
		org.jradweb.core.beans.annotation.PropertyEditor editor=readMethod.getAnnotation(org.jradweb.core.beans.annotation.PropertyEditor.class);
	    Class result=(editor==null?null:editor.editor());
	    propertyEditorCache.put(pathName, result);
	    return result;
	}
}
